package com.cloudbroker.bcs.common.event;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.cloudbroker.bcs.common.event.annotation.On;
import com.cloudbroker.bcs.common.thread.ExecutorServiceManager;
import com.cloudbroker.bcs.common.util.ConcurrentHashSet;

@Component
public class EventListenerBridge implements BeanPostProcessor, DestructionAwareBeanPostProcessor,
        ApplicationListener<BasicEvent> {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(EventListenerBridge.class);
    
    private static ExecutorService executorService;
    
    private static Map<String, ListenerInvocations> eventListenerMap = new HashMap<String, ListenerInvocations>();
    
    private static Lock eventListenerMapLock = new ReentrantLock();
    
    private static Map<Object, Set<ListenerInvocations>> targetInvocationsMap = new HashMap<Object, Set<ListenerInvocations>>();
    
    private static Lock targetInvocationsMapLock = new ReentrantLock();
    
    private static class ListenerInvocation implements Comparable<ListenerInvocation> {
        
        private Method method;
        
        private Object target;
        
        private Class<? extends BasicEvent> acceptEventClass;
        
        private Class<?> argClass;
        
        private int priority;
        
        @Override
        public int compareTo(ListenerInvocation o) {
            return priority == o.priority ? 0 : priority < o.priority ? -1 : 1;
        }
        
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("ListenerInvocation [method=").append(method).append(", target=").append(target)
                    .append(", acceptEventClass=").append(acceptEventClass).append(", argClass=").append(argClass)
                    .append(", priority=").append(priority).append("]");
            return builder.toString();
        }
        
    }
    
    private static class ListenerInvocations {
        
        private SortedSet<ListenerInvocation> listeners;
        
        private Lock lock = new ReentrantLock();
        
        private SortedSet<ListenerInvocation> getOrCreateListeners() {
            if (null == listeners) {
                lock.lock();
                try {
                    if (null == listeners) {
                        listeners = new TreeSet<ListenerInvocation>();
                    }
                } finally {
                    lock.unlock();
                }
            }
            return listeners;
        }
        
        public void addListener(ListenerInvocation listener) {
            getOrCreateListeners().add(listener);
        }
        
        @SuppressWarnings("unused")
        public void removeListener(ListenerInvocation listener) {
            if (null != listeners) {
                listeners.remove(listener);
            }
        }
        
        public SortedSet<ListenerInvocation> getListeners() {
            return listeners;
        }
        
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("ListenerInvocations [listeners=").append(listeners).append("]");
            return builder.toString();
        }
        
    }
    
    private static ListenerInvocations getListenerInvocations(String eventName) {
        ListenerInvocations listenerInvocations = eventListenerMap.get(eventName);
        if (null == listenerInvocations) {
            eventListenerMapLock.lock();
            try {
                if (null == listenerInvocations) {
                    listenerInvocations = new ListenerInvocations();
                    eventListenerMap.put(eventName, listenerInvocations);
                }
            } finally {
                eventListenerMapLock.unlock();
            }
        }
        return listenerInvocations;
    }
    
    private static Set<ListenerInvocations> getTargetInvocations(Object target) {
        Set<ListenerInvocations> targetInvocationsSet = targetInvocationsMap.get(target);
        if (null == targetInvocationsSet) {
            targetInvocationsMapLock.lock();
            try {
                if (null == targetInvocationsSet) {
                    targetInvocationsSet = new ConcurrentHashSet<ListenerInvocations>();
                    targetInvocationsMap.put(target, targetInvocationsSet);
                }
            } finally {
                targetInvocationsMapLock.unlock();
            }
        }
        return targetInvocationsSet;
    }
    
    @SuppressWarnings("unchecked")
    public static void registerListener(Object bean) {
        if (null == bean) {
            throw new IllegalArgumentException("bean can not be null");
        }
        Method[] methods = bean.getClass().getMethods();
        for (Method method : methods) {
            On onAnn = method.getAnnotation(On.class);
            if (null != onAnn) {
                // 先获取参数类型，因为这是可能失败的一环
                Class<?> argClass = null;
                Class<?>[] argTypes = method.getParameterTypes();
                if (1 < argTypes.length) {
                    // 如果不是只有一个参数则跳过
                    LOGGER.error("Method annotated by '" + On.class.getName()
                            + "' must have one parameter at most: " + method);
                    continue;
                } else if (1 == argTypes.length) {
                    argClass = argTypes[0];
                }
                // 获取事件名称
                String eventName = onAnn.eventName();
                // 如果eventName属性未设置（""），取value属性
                if (eventName.isEmpty()) {
                    eventName = onAnn.value();
                }
                // 获取可接受的事件类
                Class<? extends BasicEvent> acceptEventClass = onAnn.eventClass();
                // 如果参数类型是BasicEvent（或其子类），则可接受事件类型以参数类型为准，
                // 除非注解指明的可接受事件类型范围更小（注解指明的可接受事件类型为参数类型的子类）
                if (null != argClass && BasicEvent.class.isAssignableFrom(argClass)
                        && !argClass.isAssignableFrom(acceptEventClass)) {
                    acceptEventClass = (Class<? extends BasicEvent>) argClass;
                }
                // 获取通知优先级
                int priority = onAnn.priority();
                // 如果有@Order注解，则以其为准
                Order orderAnn = method.getAnnotation(Order.class);
                if (null != orderAnn) {
                    priority = orderAnn.value();
                }
                
                // 创建ListenerInvocation并赋值
                ListenerInvocation listenerInvocation = new ListenerInvocation();
                listenerInvocation.method = method;
                listenerInvocation.target = bean;
                listenerInvocation.acceptEventClass = acceptEventClass;
                listenerInvocation.argClass = argClass;
                listenerInvocation.priority = priority;
                
                // 获取ListenerInvocations，添加监听者
                ListenerInvocations listenerInvocations = getListenerInvocations(eventName);
                listenerInvocations.addListener(listenerInvocation);
                // 获取targetInvocationsSet，注册target与ListenerInvocations的关联，以便在bean销毁时注销ListenerInvocation
                Set<ListenerInvocations> targetInvocationsSet = getTargetInvocations(bean);
                targetInvocationsSet.add(listenerInvocations);
            }
        }
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // no op
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 当bean创建时查找并注册ListenerInvocation
        registerListener(bean);
        return bean;
    }
    
    public static void unregisterListener(Object bean) {
        // 操作都不使用锁，找不到就是没有
        // 按bean获取对应的targetInvocationsSet
        Set<ListenerInvocations> targetInvocationsSet = targetInvocationsMap.get(bean);
        // 如果对应的targetInvocationsSet存在则检查销毁ListenerInvocation
        if (null != targetInvocationsSet) {
            for (ListenerInvocations listenerInvocations : targetInvocationsSet) {
                removeInvocation(listenerInvocations.listeners, bean);
            }
        }
    }
    
    private static void removeInvocation(Set<ListenerInvocation> invocations, Object target) {
        // 对应的invocations存在才遍历
        if (null != invocations) {
            Iterator<ListenerInvocation> invocationIt = invocations.iterator();
            while (invocationIt.hasNext()) {
                ListenerInvocation invocation = invocationIt.next();
                // target相同则移除
                if (target.equals(invocation.target)) {
                    invocationIt.remove();
                }
            }
        }
    }
    
    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        // 当bean销毁时注销ListenerInvocation
        unregisterListener(bean);
    }
    
    @Override
    public void onApplicationEvent(BasicEvent event) {
        // 获取事件名称
        final String eventName = event.getName();
        // 按事件名称查找监听者
        ListenerInvocations listeners = eventListenerMap.get(eventName);
        // 如果该事件有监听者则处理
        if (null != listeners) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("notifying listners: "
                        + (null == listeners.listeners ? 0 : listeners.listeners.size()));
            }
            // 发出通知
            notifyListeners(listeners.getListeners(), event);
        }
    }
    
    private void notifyListeners(SortedSet<ListenerInvocation> listeners, final BasicEvent event) {
        // 非空则遍历
        if (null != listeners && !listeners.isEmpty()) {
            for (ListenerInvocation listener : listeners) {
                // 获取方法
                final Method method = listener.method;
                // 获取监听者bean
                final Object target = listener.target;
                // 获取可接受的事件类
                Class<? extends BasicEvent> acceptEventClass = listener.acceptEventClass;
                // 检查是否是可接受的事件类（实际事件的类为可接受的事件类的同类或子类）
                if (acceptEventClass.isAssignableFrom(event.getClass())) {
                    // 选择要传入的负载
                    final Object payload;
                    if (null == listener.argClass) {
                        // 没有参数，不接受负载
                        payload = null;
                    } else if (listener.argClass.isAssignableFrom(event.getClass())) {
                        // 负载为事件对象
                        payload = event;
                    } else {
                        // 负载为事件内容对象
                        payload = event.getPayload();
                    }
                    // 根据是否异步进行通知
                    if (event.isAsync()) {
                        // 异步通知
                        ExecutorServiceManager.submit(getExecutorService(), new Runnable() {
                            @Override
                            public void run() {
                                callNotifyEventTargetMethod(method, target, payload, event);
                            }
                        }, event.getPenetratedContextKeys());
                    } else {
                        // 同步通知
                        callNotifyEventTargetMethod(method, target, payload, event);
                    }
                }
            }
        }
    }
    
    private void callNotifyEventTargetMethod(Method method, Object target, Object payload, BasicEvent event) {
        try {
            if (null == payload) {
                method.invoke(target);
            } else {
                method.invoke(target, payload);
            }
        } catch (Exception e) {
            LOGGER.error("notify event failed, event=" + event.briefToString() + ", method=" + method
                    + ", listener=" + target, e);
        }
    }
    
    private static ExecutorService getExecutorService() {
        if (null == executorService) {
            executorService = ExecutorServiceManager.getCachedThreadPool("EventNotify");
        }
        return executorService;
    }
    
}
